#!/usr/local/bin/perl -w
use strict;
use warnings;
use utf8;

use EV;
use IO::Socket::INET;

use JSON::XS;
use HTTP::Parser::XS qw(parse_http_response HEADERS_AS_HASHREF);

use Data::Dumper;
use Client;

our $interval;
our $login;
our $inter;
our $count = 0;
our $sid;
our $port;
our $addr;
our $host;
our $path;
our $debug;
our $maxcount;
our $check_to;
our $chanel;
our $quiet;
our $run;
our $pass;
our $response;

our @options = (
	"interval=i"	=> \$interval,
	"quiet"	        => \$quiet,
	"debug:1"   	=> \$debug,
	"interactive"	=> \$inter,
	"login=s"	    => \$login,
	"port=s"	    => \$port,
	"addr=s"	    => \$addr,
	"host=s"	    => \$host,
	"path=s"	    => \$path,
	"count=i"	    => \$maxcount,
	"timeout=i"	    => \$check_to,
	"chanel=s"		=> \$chanel,
	"run=s"		    => \$run,
	"pass:s"		=> \$pass,
	"response"		=> \$response,

);
get_param(\@options);

$addr ||= "127.0.0.1";
$port ||= 22345;
$host ||= "localhost";
$maxcount ||= 1000;
$check_to ||= 60;
$chanel ||= "main";
$debug ||= 0;

my $rid = 1000;
unless($login){
	die "need login";
}
if($path){
	$path .= "/";
}else{
	$path = "";
}

$| = 1;
binmode \*STDOUT,":utf8";

my @global_watcher;
my $Server;
my $reader;
my $writer;

my %handlers;


send_message(login => {login=>$login,pass=>$pass},\&login_handler);

EV::loop;
print "\nExit normaly\n";

################################################
sub after_start {
	if($inter){
		push @global_watcher, EV::io(\*STDIN, EV::READ, \&input_handler);
	}
	push @global_watcher, EV::signal( 'INT', \&signal_handler );
	if($interval){
		run_interval($interval);;
	}
	if(!$interval || $interval > 60){
		push @global_watcher, EV::periodic 60, 60, 0, \&ping_handler;
	}
}

sub ping_handler {
	send_message(ping=>{});
}
sub run_checker {
	send_message(check=>{},\&run_checker);
}
sub send_message {
	my ($command,$message,$callback) = @_;

	unless($Server){
		my $start = time();

		$Server = IO::Socket::INET->new(
				PeerHost => $addr,
				PeerPort => $port,
				Type     => SOCK_STREAM,
			);
		unless($Server){
			print "Cannot construct socket - $@\n";
			exit;
		}
		my $end = time();
		long_connection($start,$end);
		$reader = EV::io $Server, EV::READ,\&read_handler;
		$reader->data([""]);
	}
	
	$handlers{$rid} = $callback if $callback;

	$message->{ts} = ts();
	$message->{rid} = $rid++;
	$message->{sid} = $sid if $sid;
	$message->{chanel} ||= $chanel;
	$message->{cmd} = $command;

	my $body = encode_json($message);
	if($debug > 1){
		print "SEND:\n$body\n";
	}
	$body .= chr(0);
	
	unless($writer){
		$writer = EV::io $Server, EV::WRITE,\&send_handler;
		$writer->data([$body]);
	}else{
		$writer->data()->[0] .= $body;
	}
}
sub send_handler {
	my ($w,$m) = @_;
	my $need = length($w->data->[0]);
	my $rs;
	eval {
		$rs = $w->fh->send($w->data->[0]);
	};
	if($@){
		print "ERROR: $@\n";
		exit;
	}
	if($rs){
		$need -= $rs;
		substr($w->data->[0],0,$rs,"");

		# если отправлено не все, то продолжаем ждать
		if($need > 0){
			return 0;
		}
	}
	$writer->data("");
	$writer = undef;
}
sub read_handler {
	my ($w,$m) = @_;
	my $buf;
	$w->fh->recv($buf,1024);
	if($buf){
		$w->data->[0] .= $buf;
		#(my $str = $w->data->[0]) =~ s/\x0//g;
		#print "AFTER READ:\n",$str,"\n";
		while(my $msg = parse_response(\$w->data->[0])){
			process_response($msg);
			if(my $rid = $msg->{rid}){
				if($handlers{$rid}){
					$handlers{$rid}->($msg);
					delete $handlers{$rid};
				}
			}
		}
	}
}
sub parse_response{
	my $rstr = shift;
	my $i = index($$rstr,chr(0));
	if($i == -1){
		return 0;
	}
	my $body = substr($$rstr,0,$i,"");
	substr($$rstr,0,1,"");
	my $msg;
	if($debug > 1){
		print "RECV:\n$body\n";
	}
	eval {
		$msg = decode_json($body);
	};
	if($@){
		print "Broke json: $!\n";
		exit;
	}
	return $msg;
}
__END__

sub process_response {
	my $msg = shift;
	if($msg->{type} eq "response"){
		if($msg->{error}){
			print "$msg->{ts} RESPONSE($msg->{command}:$msg->{rid}): error($msg->{error}): ",$msg->{text},"\n" if $debug;
		}else{
			print "$msg->{ts} RESPONSE($msg->{command}:$msg->{rid}): ",$msg->{text},"\n" if $msg->{text} && $debug;
		}
	}elsif($msg->{type} eq "list"){
		print "LISTSIZE=",scalar(@{$msg->{list}})," rid=$msg->{rid}\n" if $debug;
		for(sort {$a->{ats} <=> $b->{ats}} @{$msg->{list}}){
			my $str = $_->{text};
			utf8::decode $str;
			print ts(),"<",($_->{chanel}||""),"> ",$_->{ts}," [",($_->{from}||""),":$_->{rid}] ",$str,"\n" unless $quiet;
		}
	}
}
sub parse_input{
	my $str = shift;
	chomp $str;
	my $cmd = "message";
	my $msg = {};
	if($str =~ /^\s*private\s*\[([^\]]*)\]/){
		$msg->{text} = $str;
		if(my @list = grep {$_} map {s/^\s+//;s/\s+$//;$_} split /,/, $1){
			push @list,$login;
			$msg->{to} = \@list;
			$cmd = "private";
		}
	}elsif($str =~ /^\s*chanel\s+(create|remove|join|leave|list)\s+(\w+)/){
		$cmd = $1;
		$msg->{chanel} = $2;
	}elsif($str =~ s/^\s*!(\w+)//){
		$msg->{chanel} = $1;
		$msg->{text} = $str;
	}else{
		$msg->{text} = $str;
	}
	return ($cmd,$msg);
}
sub long_connection {
	my ($start,$end) = @_;
	if($debug){
		my $lc;
		if($end - $start > 30){
			$lc = ("*" x 40)."Super Very Long connect ".($end - $start)." ".("*" x 25)."\n";
		}
		elsif($end - $start > 10){
			$lc = ("*" x 30)."Very Long connect ".($end - $start)." ".("*" x 25)."\n";
		}
		elsif($end - $start > 2){
			$lc = ("*" x 20)."Long connect ".($end - $start)." ".("*" x 25)."\n";
		}
		if($lc){
			print $lc;
		}
	}
}
sub ts {
	return sprintf "%02d:%02d:%02d", (localtime)[2,1,0];
}
sub get_param {
	local $SIG{__WARN__} = sub {die "GETOPT: ".join("\n",@_)};
	GetOptions(@options) or die "$!";
}
